Procesamiento de Señales e Imagenes

Ingeniería Biomédica

Ph.D. Pablo Eduardo Caicedo Rodríguez

2025-09-04

Portada

Procesamiento de Señales e Imágenes (PSIM) Una introducción para 10.º–11.º: ver, escuchar y entender el mundo con datos.

Propósito del taller

  • Explorar qué es una señal (por ejemplo: audio, ritmo cardiaco, acelerómetro) y qué es una imagen (fotografía, rayos X).
  • Comprender, con ejemplos cotidianos, cómo se limpia, analiza y transforma la información para tomar mejores decisiones.
  • Probar una actividad práctica donde generamos y mejoramos una señal y una imagen con código visible y simple.

Enfoques didácticos sugeridos (K-12)

Teoría básica (sin matemáticas)

Señales (idea simple):

  • Son datos que cambian en el tiempo (voz, música, pasos, pulso).
  • Podemos observarlas en el tiempo (cómo suben y bajan) o por componentes (graves/agudos en audio).
  • A veces tienen ruido y usamos “filtros” para suavizar.

Imágenes (idea simple):

  • Son rejillas de puntos (píxeles).
  • Cada píxel tiene intensidad y, si es a color, combinaciones de rojo, verde y azul (RGB).
  • Podemos difuminar (suavizar), resaltar bordes o cambiar colores para destacar detalles.

Señales e imágenes en la vida diaria

Señales

  • Música y podcast (volumen, graves/agudos).
  • Sensores del teléfono (pasos, giros, aceleración).
  • Salud: pulso, respiración.

¿Qué hacemos? Suavizar ruidos, detectar patrones (por ejemplo, picos).

Imágenes

  • Fotos con filtros (contraste, nitidez).
  • Mapas de calor del clima.
  • Salud: rayos X, ultrasonido.

¿Qué hacemos? Enfocar detalles, marcar bordes, ajustar colores.

Demo 1 — Una señal con y sin ruido (matplotlib)

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(7)
fs = 200              # 'muestras por segundo'
t = np.linspace(0, 2, fs*2, endpoint=False)
signal_clean = 0.7*np.sin(2*np.pi*5*t) + 0.3*np.sin(2*np.pi*7*t)  # mezcla de tonos
noise = 0.35*rng.normal(size=t.size)
signal_noisy = signal_clean + noise

# Suavizado sencillo: promedio móvil
def moving_average(x, w=7):
    w = max(1, int(w))
    k = np.ones(w)/w
    return np.convolve(x, k, mode='same')

smooth = moving_average(signal_noisy, w=9)

plt.figure()
plt.plot(t, signal_noisy, lw=1, label='Con ruido')
plt.plot(t, smooth, lw=2, label='Suavizada (w=9)')
plt.xlabel('Tiempo (s)')
plt.ylabel('Amplitud (relativa)')
plt.legend()
plt.tight_layout()
plt.show()

Señal sintética: original con ruido vs. versión suavizada (promedio móvil).

Demo 2 — Mirar un “filtro” como imagen (seaborn)

import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt

blur3 = np.ones((3,3))/9.0
sobel_x = np.array([[-1,0,1],
                    [-2,0,2],
                    [-1,0,1]])

fig, ax = plt.subplots(1,2)
sns.heatmap(blur3, annot=True, fmt=".2f", cbar=False, ax=ax[0])
ax[0].set_title("Promedio 3×3 (suaviza)")
sns.heatmap(sobel_x, annot=True, fmt=".0f", cbar=False, ax=ax[1])
ax[1].set_title("Sobel-X (resalta bordes verticales)")
plt.tight_layout()
plt.show()

Cómo ‘se ve’ un filtro: difuminado (promedio 3×3) vs. detección de bordes (Sobel-X).

Demo 3 — Una imagen sintética y sus bordes (matplotlib)

import numpy as np
import matplotlib.pyplot as plt

# Imagen sintética (tablero + círculo) con ruido
H, W = 128, 128
yy, xx = np.indices((H, W))
checker = (((yy//8) + (xx//8)) % 2)*180
circle = ((yy - 64)**2 + (xx - 64)**2) < (28**2)
img = checker.copy().astype(float)
img[circle] = 240
rng = np.random.default_rng(5)
img_noisy = img + rng.normal(0, 12, size=img.shape)
img_noisy = np.clip(img_noisy, 0, 255)

# Convolución 2D (padding simple) con kernel promedio 3x3
kernel = np.ones((3,3))/9.0
pad = 1
padded = np.pad(img_noisy, pad_width=pad, mode='edge')
blur = np.zeros_like(img_noisy)
for i in range(H):
    for j in range(W):
        region = padded[i:i+3, j:j+3]
        blur[i, j] = np.sum(region * kernel)

# Detección de bordes (Sobel)
sx = np.array([[-1,0,1],
               [-2,0,2],
               [-1,0,1]])
sy = np.array([[-1,-2,-1],
               [ 0, 0, 0],
               [ 1, 2, 1]])

def conv2(img, k):
    pad = k.shape[0]//2
    p = np.pad(img, pad, mode='edge')
    out = np.zeros_like(img, dtype=float)
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            out[i,j] = np.sum(p[i:i+k.shape[0], j:j+k.shape[1]] * k)
    return out

gx = conv2(blur, sx)
gy = conv2(blur, sy)
edges = np.hypot(gx, gy)
edges = edges / (edges.max() + 1e-9) * 255

fig, axs = plt.subplots(1,3)
axs[0].imshow(img_noisy, cmap='gray', vmin=0, vmax=255)
axs[0].set_title("Original con ruido")
axs[1].imshow(blur, cmap='gray', vmin=0, vmax=255)
axs[1].set_title("Suavizada")
axs[2].imshow(edges, cmap='gray', vmin=0, vmax=255)
axs[2].set_title("Bordes")
for a in axs: a.axis('off')
plt.tight_layout()
plt.show()

Imagen original, versión suavizada y bordes detectados.

Actividad práctica (se ejecuta al compilar)

Reto: Ajusta un par de variables, vuelve a compilar y observa cómo cambian la señal y la imagen. La intención es mejorar la claridad sin “pasarte”: si suavizas demasiado, pierdes detalle.

import numpy as np
import matplotlib.pyplot as plt

# === Parámetros que puedes cambiar ===
win = 13           # tamaño del promedio móvil para la señal (prueba 5, 9, 13, 21)
kimg = 5           # tamaño del kernel cuadrado para suavizar la imagen (prueba 3, 5, 7)

# --- Señal ---
rng = np.random.default_rng(123)
fs = 200
t = np.linspace(0, 2, fs*2, endpoint=False)
signal_clean = 0.6*np.sin(2*np.pi*4*t) + 0.2*np.sin(2*np.pi*11*t)
noise = 0.45*rng.normal(size=t.size)
x = signal_clean + noise

def moving_average(x, w=7):
    k = np.ones(max(1,int(w)))/max(1,int(w))
    return np.convolve(x, k, mode='same')

xs = moving_average(x, win)

# --- Imagen ---
H, W = 120, 120
yy, xx = np.indices((H, W))
rect = ((yy>30)&(yy<90)&(xx>45)&(xx<75))*220
grad = (xx/xx.max())*180
img = grad.copy()
img[rect>0] = 240
img += rng.normal(0, 15, size=img.shape)
img = np.clip(img, 0, 255)

# Kernel promedio kimg×kimg
k = max(3, int(kimg))
if k % 2 == 0: k += 1
ker = np.ones((k,k))/ (k*k)
pad = k//2
p = np.pad(img, pad, mode='edge')
blur = np.zeros_like(img)
for i in range(H):
    for j in range(W):
        blur[i,j] = np.sum(p[i:i+k, j:j+k] * ker)

# Figuras
fig = plt.figure(figsize=(10,6))
gs = fig.add_gridspec(2,2)

ax1 = fig.add_subplot(gs[0, :])
ax1.plot(t, x, lw=1, label='Con ruido')
ax1.plot(t, xs, lw=2, label=f'Suavizada (w={win})')
ax1.set_title("Señal: antes vs. después")
ax1.set_xlabel("Tiempo (s)"); ax1.set_ylabel("Amplitud (relativa)")
ax1.legend()

ax2 = fig.add_subplot(gs[1, 0])
ax2.imshow(img, cmap='gray', vmin=0, vmax=255)
ax2.set_title("Imagen ruidosa")
ax2.axis('off')

ax3 = fig.add_subplot(gs[1, 1])
ax3.imshow(blur, cmap='gray', vmin=0, vmax=255)
ax3.set_title(f"Imagen suavizada (kernel {k}×{k})")
ax3.axis('off')

plt.tight_layout()
plt.show()

Ajusta las variables y recompila: compara resultados.

Instrucciones para el aula (rápidas):

  1. Cambia win (señal) y kimg (imagen) en el bloque anterior.

  2. Vuelve a compilar/renderizar.

  3. En parejas, respondan:

    • ¿Qué valor “mejor equilibra” claridad y detalle en la señal?
    • ¿Qué valor “mejor equilibra” claridad y detalle en la imagen?
    • ¿En qué casos suavizar demasiado es un problema?

Cierre (ideas clave)

  • Señales: historias en el tiempo; Imágenes: mosaicos de píxeles.
  • Filtros simples ayudan a ver mejor: suavizar, resaltar bordes, ajustar color.
  • Exceso de suavizado borra detalles; insuficiente deja ruido.
  • La intuición visual es el primer paso antes de técnicas avanzadas.

Recursos y fuentes (K-12, sin matemáticas)

Guion narrativo (para la docencia)

  • Inicio: conectar con su mundo: “música con filtros”, “mejores fotos en el móvil”, “pruebas médicas por imágenes”.
  • Exploración: definir sin fórmulas qué es señal e imagen con ejemplos cercanos.
  • Demostraciones: una señal ruidosa y una imagen con ruido; mostrar mejoras con filtros simples.
  • Actividad: manipular win y kimg, recompilar, comparar resultados y justificar elecciones.
  • Cierre: “no hay magia: hay decisiones” (equilibrio entre ruido y detalle).
  • Extensión: visitar PhET o Khan Academy para profundizar en casa.